+ function ($) {
	
	function stringify(value) {
    	if (value == null || $.trim(value) == "") { return ""; }
    	if ($.isArray(value)) { return value.length ? JSON.stringify(value) : ""; }
    	if ($.isPlainObject(value)) { // { config:{}, items:[] } 必须返回空字符串, 否则必填校验会失效
    		var buffer = {};
    		for (var k in value) {
    			var v = value[k];
    			if (v == null || $.trim(v) == "") { continue; }
    			if ($.isArray(v) && v.length == 0) { continue; }
    			if ($.isPlainObject(v) && $.isEmptyObject(v)) { continue; }
    			buffer[k] = value[k];
    		}
    		return $.isEmptyObject(buffer) ? "" : JSON.stringify(buffer);
    	}
        return value;
	};
	
	function parseJson(value, throwOnFormatError) {
    	if ($.isArray(value) || $.isPlainObject(value)) { return value; } 
    	if (value == null || $.trim(value) == "") { return null; }
		try {
    		return (new Function("return " + value))(); // JSON.parse()的KEY也必须要有引号
		} catch (e) {
			if (throwOnFormatError) { throw e; }  else { return null; }
		}
	};

    function findPanel(elem, selector) {
		if (!selector) return $(document.body);
    	 if ($.dt.isJqueryElement(selector)) { return selector; }
		if ($.dt.isDomElement(selector)) { return $(selector); }
		if (selector == "this") return $(elem);
		if (selector == "parent") return $(elem).parent();
		if (selector == "root") return $(document);
		if (selector == "body") return $(document.body);
		return $(elem).closest(selector);
	};

	// 下拉弹出框中有一个表单, 点保存时生成json数据
	+function ($) {
		$.parser.plugins.push("comboform");
		$.fn.form.defaults.fieldTypes.push("comboform");
	
	    function setValue(elem, value) {
	    	var v = $.data(elem, "comboform");
	        var opts = v.options;
	        var oldString = opts.stringify.call(elem, v.data);
	        var newValue = opts.parser.call(elem, value);
	        var newString = opts.stringify.call(elem, newValue);
	        if (oldString == newString) { return; }
	
	        var oldValue = v.data;
	        v.data = opts.onHandleData.call(elem, newValue);
	
	        $(elem).combo("setText", opts.formatter.call(elem, v.data));
	        $(elem).combo("setValue", opts.stringify.call(elem, v.data));
	        var panel = $(elem).combo("panel");
	        if (value) {
	        	panel.find("form").form("load", v.data);
	        } else {
	        	panel.find("form").form("clear");
	        }
	        opts.onValueChange.call(elem, newValue, oldValue);
	    };
	
	    function renderPanel(elem) {
	        var v = $.data(elem, "comboform");
	        var opts = v.options;
	        var me = $(elem);
	        me.addClass("comboform-f");
	        me.combo($.extend({}, opts));
	        var panel = me.combo("panel");
	        panel.addClass("comboform-panel");
	        if (opts.cls) { panel.addClass(opts.cls); }
	        var result = opts.onBeforeRenderPanel.call(elem, v.data);
	        if (result === false) return;
	        if (opts.panelTemplate) {
	            panel.html(opts.generateHtml.call(elem, opts.panelTemplate, { options:opts, data:v.data }));
	        }
	        opts.onAfterRenderPanel.call(elem, panel);
	        opts.onBindEvent.call(elem, panel);
	    };
	    $.fn.comboform = function (options, methodParam) {
	        if (typeof options == "string") {
	            var method = $.fn.comboform.methods[options];
	            if (method) {
	                return method(this, methodParam);
	            } else {
	                return this.combo(options, methodParam);
	            }
	        }
	        options = options || {};
	        return this.each(function () {
	            var v = $.data(this, "comboform");
	            if (v) {
	                $.extend(v.options, options);
	            } else {
	                v = $.data(this, "comboform", {
	                    options: $.extend({}, $.fn.comboform.defaults, $.fn.comboform.parseOptions(this), options),
	                    data: {}, templates: {}
	                });
	                if (options.value != undefined) {
	                	v.options.originalData = options.value;
	                }
	            }
	            renderPanel(this);
	            setValue(this, options.originalData);
	        });
	    };
	    $.fn.comboform.methods = {
	        options: function (jq) {
	            var opts = jq.combo("options");
	            return $.extend($.data(jq[0], "comboform").options, {
	                width: opts.width,
	                height: opts.height,
	                originalData: opts.originalData,
	                disabled: opts.disabled,
	                readonly: opts.readonly
	            });
	        }, getValues: function (jq) {
	        	return $.data(jq[0], "comboform").data;
	        }, getValue: function (jq) {
	        	var self = jq[0];
	        	var v = $.data(self, "comboform");
	            return v.options.stringify.call(self, v.data);
	        }, setValues: function (jq, items) {
	            return jq.each(function () {
	                setValue(this, items);
	            });
	        }, setValue: function (jq, value) {
	            return jq.each(function () {
	                setValue(this, value);
	            });
	        }, clear: function (jq) {
	            return jq.each(function () {
	                $(this).combo("clear").comboform("setValue", null);
	            });
	        }, reset: function (jq) {
	            return jq.each(function () {
	                var opts = $(this).comboform("options");
	                setValue(this, opts.originalData);
	            });
	        }
	    };
	    $.fn.comboform.parseOptions = function (elem) {
	        return $.extend({}, $.fn.combo.parseOptions(elem), $.parser.parseOptions(elem, 
	        	[ "panelTemplate" ]
	        ));
	    };
	    $.fn.comboform.defaults = $.extend({}, $.fn.combo.defaults, {
	    	panelTemplate: null, // <div class="combo-form"><form></form></div>
	        // value格式化为字符串
	        stringify: stringify,
	        // value解析为JsonObject
	    	parser: parseJson,
	        // data转换为文本, 用于setText调用
	        formatter: function(data) {
	        	if (value == null || $.trim(value) == "" || $.isEmptyObject(value)) { return ""; }
	            var opts = $(this).comboform("options");
	            return opts.stringify.call(this, data);
	        },
	        generateHtml: function(type, data) {
	            var v = $.data(this, "comboform");
	            var opts = v.options;
	
	            var space = findPanel(this, opts.space);
	    		if (!v.templates[type]) {
	    			v.templates[type] = baidu.template(space.find("script[type='" + type + "']").html());
	    		}
	    		return v.templates[type](data);
	        },
	        onHandleData: function(data) { return data; },
	        onValueChange: function(newValue, oldValue) {},
	        onBindEvent: function(panel) {},
	        onBeforeRenderPanel: function (value) {}, 
	        onAfterRenderPanel: function (panel) {}
	    });
	}(jQuery);
	
	
	
	
	
	
	
	// 下拉框, 收集列表数据, 点创建按钮在下拉框中生成表单, 保存时在列表中增加一项数据
	+function ($) {
		$.parser.plugins.push("combocollect");
		$.fn.form.defaults.fieldTypes.push("combocollect");

	    // data = { config:JsonObject, items:JsonArray }
	    function setData(elem, data) {
	    	var v = $.data(elem, "combocollect");
	        var opts = v.options;
	        var oldData = v.data;
	        var oldString = opts.stringify.call(elem, v.data);
	        var tmpData = $.extend({ config:undefined, items:[] }, opts.parser.call(elem, data));
	        var newData = opts.onHandleData.call(elem, tmpData);
	        var newString = opts.stringify.call(elem, newData);
	        if (oldString == newString) { return; }

	        v.data = newData;
	        var panel = $(elem).combo("panel");
	        $(elem).combo("setText", opts.formatter.call(elem, newData));
	        $(elem).combo("setValue", newString);

	        if (opts.stringify.call(elem, oldData.items) != opts.stringify.call(elem, newData.items)) {
	        	renderAddItems(elem, newData.items, false);
		        opts.onItemChange.call(elem, newData.items, oldData.items);
	        }
	        if (opts.stringify.call(elem, oldData.config) != opts.stringify.call(elem, newData.config)) {
	        	renderConfig(elem, newData.config);
		        opts.onConfigChange.call(elem, newData.config, oldData.config);
	        }
	    };
	    
	    function getItem(elem, key) {
	    	var v = $.data(elem, "combocollect");
	    	for (var i = 0; i < v.data.items.length; i++) {
	    		if (key == v.data.items[i][v.options.valueField]) {
	    			return v.data.items[i];
	    		}
	    	}
        	return null;
	    };
	    
	    // items = JsonArray
	    function addItems(elem, items) {
	    	var v = $.data(elem, "combocollect");
	        var opts = v.options;
	        var addItems = opts.parser.call(elem, items);
	        if (!addItems || !addItems.length) { return; }

	        var oldData = $.extend(true, {}, v.data);
	    	var newData = $.extend(true, {}, v.data);
	        Array.prototype.push.apply(newData.items, addItems);
	        v.data = opts.onHandleData.call(elem, newData);
	        $(elem).combo("setText", opts.formatter.call(elem, v.data));
	        $(elem).combo("setValue", opts.stringify.call(elem, v.data));
	        renderAddItems(elem, addItems, true);
	        opts.onItemChange.call(elem, v.data.items, oldData.items);
	        if (opts.stringify.call(elem, oldData.config) != opts.stringify.call(elem, v.data.config)) {
	        	renderConfig(elem, v.data.config);
		        opts.onConfigChange.call(elem, v.data.config, oldData.config);
	        }
	    };
	    
	    // json = { key:oldKey, item:JsonObject } 
	    function updateItem(elem, json) {
	    	var v = $.data(elem, "combocollect");
	        var opts = v.options;
	        var key = json[opts.valueField] || json.key;
	        var newValue = opts.parser.call(elem, json.item);
	        var oldData = $.extend(true, {}, v.data);
	    	var newData = $.extend(true, {}, v.data);
	        var oldItem = null;
	    	for (var i = 0; i < newData.items.length; i++) {
	    		if (key == newData.items[i][opts.valueField]) {
	    			oldItem = newData.items[i];
	    			newData.items[i] = newValue;
	    			break;
	    		}
	    	}
	    	if (oldItem == null) { return; }

	    	v.data = opts.onHandleData.call(elem, newData);
	        $(elem).combo("setText", opts.formatter.call(elem, v.data));
	        $(elem).combo("setValue", opts.stringify.call(elem, v.data));
	        renderUpdateItem(elem, key, newValue);
	        opts.onItemChange.call(elem, v.data.items, oldData.items);
	        if (opts.stringify.call(elem, oldData.config) != opts.stringify.call(elem, v.data.config)) {
	        	renderConfig(elem, v.data.config);
		        opts.onConfigChange.call(elem, v.data.config, oldData.config);
	        }
	    };
	    
	    // item = key or JsonObject
	    function removeItem(elem, item) {
	    	var v = $.data(elem, "combocollect");
	        var opts = v.options;
	    	var key = item;
	    	if ($.isPlainObject(item)) { key = item[opts.valueField]; }
	    	var index = null;
	    	for (var i = 0; i < v.data.items.length; i++) {
	    		if (key == v.data.items[i][opts.valueField]) {
	    			index = i; break;
	    		}
	    	}
	    	if (index == null) { return; }

	        var oldData = $.extend(true, {}, v.data);
	    	var newData = $.extend(true, {}, v.data);
	    	newData.items.splice(index, 1);
	    	v.data = opts.onHandleData.call(elem, newData);
	        $(elem).combo("setText", opts.formatter.call(elem, v.data));
	        $(elem).combo("setValue", opts.stringify.call(elem, v.data));
	        renderRemoveItems(elem, [key]);
	        opts.onItemChange.call(elem, v.data.items, oldData.items);
	        if (opts.stringify.call(elem, oldData.config) != opts.stringify.call(elem, v.data.config)) {
	        	renderConfig(elem, v.data.config);
		        opts.onConfigChange.call(elem, v.data.config, oldData.config);
	        }
	    };

		function renderRemoveItems(elem, keys) {
	        var panel = $(elem).combo("panel").find(".combo-items");
	        panel.find(".combo-item").each(function() {
	        	var key = $(this).attr("data-key");
	        	if ($.inArray(key, keys) >= 0) {
	        		$(this).remove();
	        	}
	        });
		};
		
		function renderUpdateItem(elem, key, item) {
	        var v = $.data(elem, "combocollect");
	        var opts = v.options;
	        var result = opts.onBeforeRenderItems.call(elem, [item], "update");
	        if (result === false) return;

	        var panel = $(elem).combo("panel").find(".combo-items");
	        if (opts.itemTemplate) {
	        	var html = opts.generateHtml.call(elem, opts.itemTemplate, $.extend({ $options:opts }, item));
	        	panel.find(".combo-item[data-key='"+key+"']").replaceWith(html);
	        }
	        opts.onAfterRenderItems.call(elem, panel);
		};

	    function renderAddItems(elem, items, append) {
	        var v = $.data(elem, "combocollect");
	        var opts = v.options;
	        var result = opts.onBeforeRenderItems.call(elem, items, append ? "append" : "reset");
	        if (result === false) return;
	
	        var panel = $(elem).combo("panel").find(".combo-items");
	        if (opts.itemTemplate) {
	        	if (!items.length) { // 清空
	        		panel.find(".combo-item").remove();
	        	} else {
	                var buffer = [];
	                $.each(items, function() {
	                	var data = $.extend({ $options:opts, $config:v.data.config, $items:v.data.items }, this);
	                	buffer.push(opts.generateHtml.call(elem, opts.itemTemplate, data));
	                });
	                var html = buffer.join("");
	                if (append) { // 追加
	                	var last = panel.find(".combo-item:last");
	                	if (last.length) {
	                		last.after(html);
	                	} else {
	                		panel.prepend(html);
	                	}
	                } else { // 替换
	                	panel.find(".combo-item").remove();
	                	panel.prepend(html);
	                }
	        	}
	        }
	        opts.onAfterRenderItems.call(elem, panel);
	    };

	    function renderConfig(elem, config) {
	        var v = $.data(elem, "combocollect");
	        var opts = v.options;
	        var result = opts.onBeforeRenderConfig.call(elem, config);
	        if (result === false) return;
	
	        var panel = $(elem).combo("panel").find(".combo-items");
	        if (opts.configTemplate) {
	        	var html = opts.generateHtml.call(elem, opts.configTemplate, $.extend({ $options:opts }, v.data));
        		panel.append(html);
	        }
	        opts.onAfterRenderConfig.call(elem, panel);
	    };
	
	    function renderPanel(elem) {
	        var v = $.data(elem, "combocollect");
	        var opts = v.options;
	        var me = $(elem);
	        me.addClass("combocollect-f");
	        me.combo($.extend({}, opts));
	        var panel = me.combo("panel");
	        panel.addClass("combocollect-panel");
	        if (opts.cls) { panel.addClass(opts.cls); }
	        var result = opts.onBeforeRenderPanel.call(elem, v.data);
	        if (result === false) return;
	        if (opts.panelTemplate) {
	            panel.html(opts.generateHtml.call(elem, opts.panelTemplate, { options:opts, data:v.data }));
	        }
	        opts.onAfterRenderPanel.call(elem, panel);
	        opts.onBindEvent.call(elem, panel);
	    };

	    $.fn.combocollect = function (options, methodParam) {
	        if (typeof options == "string") {
	            var method = $.fn.combocollect.methods[options];
	            if (method) {
	                return method(this, methodParam);
	            } else {
	                return this.combo(options, methodParam);
	            }
	        }
	        options = options || {};
	        return this.each(function () {
	            var v = $.data(this, "combocollect");
	            if (v) {
	                $.extend(v.options, options);
	            } else {
	                v = $.data(this, "combocollect", {
	                    options: $.extend({}, $.fn.combocollect.defaults, $.fn.combocollect.parseOptions(this), options),
	                    data: { config:undefined, items:[] }, templates: {}
	                });
	                if (options.value != undefined) {
	                	v.options.originalData = options.value;
	                }
	            }
	            renderPanel(this);
                setData(this, options.originalData);
	        });
	    };
	    $.fn.combocollect.methods = {
	        options: function (jq) {
	            var opts = jq.combo("options");
	            return $.extend($.data(jq[0], "combocollect").options, {
	                width: opts.width,
	                height: opts.height,
	                originalData: opts.originalData,
	                disabled: opts.disabled,
	                readonly: opts.readonly
	            });
	        }, getData: function (jq, type) { // combocollect("getData") or combocollect("getData", "items or config");
	        	var v = $.data(jq[0], "combocollect");
	        	return type == undefined ? v.data : v.data[type];
	        }, setConfig: function(jq, config) {
	            return jq.each(function () {
		        	var v = $.data(this, "combocollect");
		        	setData(this, $.extend({}, v, { config:config }));
	            });
	        }, getValues: function (jq) {
	        	var v = $.data(jq[0], "combocollect");
	        	return v.data;
	        }, getValue: function (jq) {
	        	var self = jq[0];
	        	var v = $.data(self, "combocollect");
	            return v.options.stringify.call(self, v.data);
	        }, setValues: function (jq, data) {
	            return jq.each(function () {
	                setData(this, data);
	            });
	        }, setValue: function (jq, data) {
	            return jq.each(function () {
	                setData(this, data);
	            });
	        }, getItem: function (jq, key) { // combocollect("getItem", "key");
	        	return getItem(jq[0], key);
	        }, addItems: function(jq, items) {
	            return jq.each(function () {
	            	addItems(this, items);
	            });
	        }, addItem: function(jq, item) {
	            return jq.each(function () {
	            	addItems(this, [item]);
	            });
	        }, updateItem: function(jq, json) { // json = { key:oldKey, item:JsonObject } 
	            return jq.each(function () {
	            	updateItem(this, json);
	            });
	        }, removeItem: function(jq, key) {
	            return jq.each(function () {
	            	removeItem(this, key);
	            });
	        }, clear: function (jq) {
	            return jq.each(function () {
	                $(this).combo("clear").combocollect("setValues", null);
	            });
	        }, reset: function (jq) {
	            return jq.each(function () {
	                var opts = $(this).combocollect("options");
	                setData(this, opts.originalData);
	            });
	        }
	    };
	    $.fn.combocollect.parseOptions = function (elem) {
	        return $.extend({}, $.fn.combo.parseOptions(elem), $.parser.parseOptions(elem, 
	        	[ "panelTemplate", "itemTemplate", "valueField", "textField", { maxItems: "number" } ]
	        ));
	    };
	    $.fn.combocollect.defaults = $.extend({}, $.fn.combo.defaults, {
	    	maxItems: 0, // 最多允许数量(0表示无限制)
	    	panelTemplate: null, // <div><ul class="combo-items"></ul><div class="combo-operate do-create"></div></div>
	    	itemTemplate: null, // <li class="combo-item" data-key="value"></li>
	    	valueField: "value",
	        textField: "text",
	        // value格式化为字符串
	        stringify: stringify,
	        // value解析为JsonObject
	    	parser: parseJson,
	        // data转换为文本, 用于setText调用
	        formatter: function(data) {
	            var opts = $(this).combocollect("options");
	            var buffer = [];
	            $.each(data.items || [], function(i, v) {
	            	buffer.push(v[opts.textField]);
	            });
	            return buffer.join(opts.separator);
	        },
	        generateHtml: function(type, data) {
	            var v = $.data(this, "combocollect");
	            var opts = v.options;
	
	            var space = findPanel(this, opts.space);
	    		if (!v.templates[type]) {
	    			v.templates[type] = baidu.template(space.find("script[type='" + type + "']").html());
	    		}
	    		return v.templates[type](data);
	        },
	        onBindEvent: function(panel) {},
	        onHandleData: function(data) { return data; },
	        onItemChange: function(newItems, oldItems) {},
	        onConfigChange: function(newConfig, oldConfig) {},
	        onBeforeRenderPanel: function (items) {}, 
	        onAfterRenderPanel: function (panel) {},
	        onBeforeRenderItems: function (items) {}, 
	        onAfterRenderItems: function (panel) {},
	        onBeforeRenderConfig: function (config) {}, 
	        onAfterRenderConfig: function (panel) {}
	    });
	}(jQuery);


}(jQuery);
